/******************************************************************************
*  Copyright(c) 2021-2023 Sietium
*
*  @file:    fill.cpp
*  @brif:    fill cpp
*
*  @date:    09/01/2021
*  @Author:  cary.Zhang
*
******************************************************************************/
#include <string>
#include <cstdlib>
#include <cmath>
#include <vector>
#include <array>
#include <memory>
#include "vmath.h"
#include "glad/gl-headers.h"

#include "glslprogram.h"
#include "node.h"
#include "texture.h"
#include "objectfactory.h"

class Fill: public Node
{
    static constexpr int COUNT = 100;
    static constexpr int MAX_SIZE = 6;
public:
    Fill();
protected:
    NodeType getNodeType();
    bool startup();
    void render(double currentTime, double difTime);
    void shutdown();
private:
    void perfCheckerTexture(GLsizei width, GLsizei height, int index);
    void initializeVertexData();
    void drawArrays(int count);
protected:
    GLSLProgram program;
    GLuint vao = 0;
    GLuint vbo = 0;
    int currentTexture = 0;
    bool currentDirectionUp = true;
    GLuint textures[MAX_SIZE];

    float vertices[32] = {
        /*x     y     s    t     r    g    b    a  */
        -1.0, -1.0,  0.0, 0.0,  1.0, 0.0, 0.0, 1,
            1.0, -1.0,  1.0, 0.0,  0.0, 1.0, 0.0, 1,
            1.0,  1.0,  1.0, 1.0,  1.0, 0.0, 0.0, 1,
            -1.0,  1.0,  0.0, 1.0,  0.0, 0.0, 1.0, 1,
        };
};

Fill::Fill()
{
    nodeName = "Fill";
}

Node::NodeType Fill::getNodeType()
{
    return NodeType_FillRate;
}

void Fill::perfCheckerTexture(GLsizei width, GLsizei height, int index)
{
    auto image = new unsigned char[width * height * 4];
    GLint i, j, k;

    k = 0;
    for (i = 0; i < height; i++) {
        for (j = 0; j < width; j++) {
            GLubyte color;
            if (((i / 32) ^ (j / 32)) & 1)
                color = 0xff;
            else
                color = 0x0;

            image[k++] = color;
            image[k++] = color;
            image[k++] = color;
            image[k++] = 0xff;
        }
    }

    glBindTexture(GL_TEXTURE_2D, textures[index]);

    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
    glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, image);

    delete []image;
}

bool Fill::startup()
{
    if (PerfWindow::glVersion < 330) {
        printf("Failed! The GL version is less than 3.3 !\n");
        return false;
    }

    bool ok = program.build("../media/shaders/fill/fill.vs.glsl", "../media/shaders/fill/fill.fs.glsl");
    if (!ok)
        return false;

    glGenVertexArrays(1, &vao);
    glBindVertexArray(vao);

    glGenBuffers(1, &vbo);
    glBindBuffer(GL_ARRAY_BUFFER, vbo);

    glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);

    glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 8 * sizeof(float), (void *)0);
    glEnableVertexAttribArray(0);

    glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, 8 * sizeof(float), (void *)(2 * sizeof(float)));
    glEnableVertexAttribArray(1);

    glVertexAttribPointer(2, 4, GL_FLOAT, GL_FALSE, 8 * sizeof(float), (void *)(4 * sizeof(float)));
    glEnableVertexAttribArray(2);

    glGenTextures(MAX_SIZE, textures);

    for (int i = 0; i < MAX_SIZE; i++)
        perfCheckerTexture(64 * std::pow(2, i + 1), 64 * std::pow(2, i + 1), i);

    glClearColor(0.0, 0.0, 0.0, 0.0);

    glActiveTexture(GL_TEXTURE0);
    glBindTexture(GL_TEXTURE_2D, textures[0]);

    return true;
}

void Fill::drawArrays(int count)
{
    glBindVertexArray(vao);

    for (int i = 0; i < count; i++)
        glDrawArrays(GL_TRIANGLE_FAN, 0, 4);
}

void Fill::render(double currentTime, double difTime)
{
    program.use();

    glActiveTexture(GL_TEXTURE0);
    glBindTexture(GL_TEXTURE_2D, textures[currentTexture]);

    program.setUniform1f("time", currentTime);
    program.setUniform1f("size", currentTexture);
    drawArrays(COUNT);

    if (currentDirectionUp)
        currentTexture ++;
    else
        currentTexture --;

    if (currentTexture >= MAX_SIZE) {
        currentTexture -= 2;
        currentDirectionUp = false;
    } else if (currentTexture < 0) {
        currentTexture += 2;
        currentDirectionUp = true;
    }
}

void Fill::shutdown()
{
    glDeleteTextures(MAX_SIZE, textures);
    glDeleteBuffers(1, &vbo);
    glDeleteVertexArrays(1, &vao);
    program.clear();

    glUseProgram(0);
    glActiveTexture(GL_TEXTURE0);
    glBindTexture(GL_TEXTURE_2D, 0);
    glBindVertexArray(0);
}

REGISTER_OBJECT(Node, Fill)

